﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;

namespace Isuka.Spiritual.Auxiliaries
{

    using System.Diagnostics.CodeAnalysis;
    using LocalRule = CharRules<NumberFormatInfo>;
    using LocalRuleHolder = RuleHolder<char, NumberFormatInfo>;

    public class NumberParserInfo
    {
        NumberStyles styles;
        NumberFormatInfo formatInfo;

        public static readonly NumberParserInfo Integer
            = new NumberParserInfo(NumberStyles.Integer);

        public NumberFormatInfo FormatInfo
        {
            get
            {
                Contract.Ensures(Contract.Result<NumberFormatInfo>() != null);
                return formatInfo;
            }
            set
            {
                Contract.Requires<ArgumentNullException>(value != null, "NumberFormatInfo");
                formatInfo = value;
            }
        }

        public static readonly NumberParserInfo HexNumber = new NumberParserInfo(NumberStyles.HexNumber);

        public static readonly NumberParserInfo Real = new NumberParserInfo(NumberStyles.Float);


        public NumberStyles Styles
        {
            get { return styles; }
            set { styles = value; }
        }

        public NumberParserInfo(NumberStyles styles)
        {
            this.styles = styles;
            this.formatInfo = NumberFormatInfo.InvariantInfo.Copy();
        }

        public NumberParserInfo(NumberStyles styles, NumberFormatInfo info)
        {
            Contract.Requires<ArgumentNullException>(info != null, "info");

            this.styles = styles;
            this.formatInfo = info;
        }

        /// <summary>
        /// 通貨記号
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        LocalRuleHolder Currency(LocalRuleHolder n)
        {
            Contract.Requires(n != null);
            Contract.Ensures(Contract.Result<LocalRuleHolder>() != null);

            if (Styles.HasFlag(NumberStyles.AllowCurrencySymbol))
            {
                var ns = FormatInfo.NegativeSign;
                var ps = FormatInfo.PositiveSign;
                var cs = FormatInfo.CurrencySymbol;

                var negative = LocalRule.Fail;
                switch (FormatInfo.CurrencyNegativePattern)
                {
                    case 0:
                        negative = ('(' + cs) % n % ')';
                        break;
                    case 1:
                        negative = (ns + cs) % n;
                        break;
                    case 2:
                        negative = (cs + ns) % n;
                        break;
                    case 3:
                        negative = cs % n % ns;
                        break;
                    case 4:
                        negative = '(' % n % (cs + ')');
                        break;
                    case 5:
                        negative = ns % n % cs;
                        break;
                    case 6:
                        negative = n % (ns + cs);
                        break;
                    case 7:
                        negative = n % (cs + ns);
                        break;
                    case 8:
                        negative = ns % n % (' ' + cs);
                        break;
                    case 9:
                        negative = (ns + cs + ' ') % n;
                        break;
                    case 10:
                        negative = n % (' ' + cs + ns);
                        break;
                    case 11:
                        negative = (cs + ' ') % n % ns;
                        break;
                    case 12:
                        negative = (cs + ' ' + ns) % n;
                        break;
                    case 13:
                        negative = n % (ns + ' ' + cs);
                        break;
                    case 14:
                        negative = ('(' + cs + ' ') % n % ')';
                        break;
                    case 15:
                        negative = '(' % n % (' ' + cs + ')');
                        break;
                    default:
                        break;
                }

                var positive = LocalRule.Fail;
                switch (FormatInfo.CurrencyPositivePattern)
                {
                    case 0:
                        positive = cs % n;
                        break;
                    case 1:
                        positive = n % cs;
                        break;
                    case 2:
                        positive = (cs + ' ') % n;
                        break;
                    case 3:
                        positive = n % (' ' + cs);
                        break;
                    default:
                        break;
                }

                return negative | positive | n;
            }
            else
            {
                return n;
            }
        }

        /// <summary>
        /// 数字の本体
        /// </summary>
        /// <typeparam name="A"></typeparam>
        /// <returns></returns>
        LocalRuleHolder DigitsBody()
        {
            Contract.Ensures(Contract.Result<LocalRuleHolder>() != null);

            var expr = Styles.HasFlag(NumberStyles.AllowThousands)
                        ? LocalRule.IntegralDigits(FormatInfo.NumberGroupSeparator)
                        : LocalRule.Digits()
                        ;

            if (Styles.HasFlag(NumberStyles.AllowDecimalPoint))
            {
                var fp = LocalRule.Eq(FormatInfo.NumberDecimalSeparator);
                var frac = fp % LocalRule.Digits();
                expr %= frac.ZeroOrOne();
            }

            if (Styles.HasFlag(NumberStyles.AllowExponent))
            {
                var expo = LocalRule.Case('e', 'E');
                expo %= Sign(LocalRule.Digits());
                expr %= expo.ZeroOrOne();
            }

            return expr;
        }

        LocalRuleHolder CurrencyOrSign(LocalRuleHolder n)
        {
            Contract.Requires(n != null);
            Contract.Ensures(Contract.Result<LocalRuleHolder>() != null);

            return Currency(n) | Sign(n);
        }

        public RuleHolder<char, A> MakeNumberParser<A>()
            where A : class
        {
            Contract.Ensures(Contract.Result<RuleHolder<char, A>>() != null);

            // http://msdn.microsoft.com/ja-jp/library/c09yxbyt.aspx
            var rule = (Styles.HasFlag(NumberStyles.AllowHexSpecifier))
                // [ws]hexdigits[ws]
                        ? Whitespace(LocalRule.HexDigits()).Rule
                // [ws][$][sign][digits,]digits[.fractional_digits][e[sign]exponential_digits][ws]
                        : Whitespace(RealSymbol(CurrencyOrSign(DigitsBody()))).Rule
                        ;

            return new RuleHolder<char, A>((i, c, s) =>
            {
                var nc = c.RebindArg(FormatInfo);
                return rule(i, nc, LocalRule.NoSkip);
            });
        }

        /// <summary>
        /// 無限大・NaN記号
        /// </summary>
        /// <param name="expr"></param>
        /// <returns></returns>
        LocalRuleHolder RealSymbol(LocalRuleHolder expr)
        {
            Contract.Requires(expr != null);
            Contract.Ensures(Contract.Result<LocalRuleHolder>() != null);

            if (Styles.HasFlag(NumberStyles.AllowDecimalPoint) && Styles.HasFlag(NumberStyles.AllowExponent))
            {
                expr |= FormatInfo.PositiveInfinitySymbol;
                expr |= FormatInfo.NegativeInfinitySymbol;
                expr |= FormatInfo.NaNSymbol;
            }
            return expr;
        }

        /// <summary>
        /// 符号
        /// </summary>
        /// <param name="expr"></param>
        /// <returns></returns>
        LocalRuleHolder Sign(LocalRuleHolder expr)
        {
            Contract.Requires(expr != null);
            Contract.Ensures(Contract.Result<LocalRuleHolder>() != null);

            var sign = (LocalRule.Eq(FormatInfo.PositiveSign) | FormatInfo.NegativeSign).ZeroOrOne();
            if (Styles.HasFlag(NumberStyles.AllowLeadingSign))
            {
                expr = sign % expr;
            }
            if (Styles.HasFlag(NumberStyles.AllowTrailingSign))
            {
                expr %= sign;
            }
            if (Styles.HasFlag(NumberStyles.AllowParentheses))
            {
                expr = '(' % expr % ')' | expr;
            }
            return expr;
        }

        /// <summary>
        /// スペース
        /// </summary>
        /// <param name="expr"></param>
        /// <returns></returns>
        LocalRuleHolder Whitespace(LocalRuleHolder expr)
        {
            Contract.Requires(expr != null);
            Contract.Ensures(Contract.Result<LocalRuleHolder>() != null);

            var ws = LocalRule.While(x => x == 0x9 || x == 0xA || x == 0xB || x == 0xC || x == 0xD || x == 0x20, 0);
            if (Styles.HasFlag(NumberStyles.AllowLeadingWhite))
            {
                expr = ws % expr;
            }
            if (Styles.HasFlag(NumberStyles.AllowTrailingWhite))
            {
                expr %= ws;
            }
            return expr;
        }

        #region ContractInvariantMethod

        [ContractInvariantMethod]
        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
        private void ObjectInvariant()
        {
            Contract.Invariant(formatInfo != null);
        }

        #endregion

    }
}
